/*
* This file is a part of the open-eBackup project.
* This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
* If a copy of the MPL was not distributed with this file, You can obtain one at
* http://mozilla.org/MPL/2.0/.
*
* Copyright (c) [2024] Huawei Technologies Co.,Ltd.
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*/
#include <string>
#include <utility>
#include "volume_handlers/oceanstor/DiskScannerHandler.h"
#include "FusionStorageIscsiDiskScanner.h"
#include "common/utils/Utils.h"

VIRT_PLUGIN_NAMESPACE_BEGIN

namespace {
const int32_t WWN_POS_OFFSET = 6;
const int32_t NAA_POS_OFFSET = 4;
const int32_t ISCSI_SESSION_PORTAL_OFFSET = 7;
const int32_t WAIT_RESCAN_TIME = 5;
const int32_t END_FIND_POS = -1;

const std::string ISCSI_TARGET_PORT = "3260";
const std::string D_FINDDISK = "../";
const std::string ISCSI_SESSION_PORTAL = "portal";

using Defer = std::shared_ptr<void>;
}  // namespace

FusionStorageIscsiDiskScanner *FusionStorageIscsiDiskScanner::GetInstance()
{
    static FusionStorageIscsiDiskScanner instance;
    return &instance;
}

FusionStorageIscsiDiskScanner::FusionStorageIscsiDiskScanner()
{}

FusionStorageIscsiDiskScanner::~FusionStorageIscsiDiskScanner()
{}

int32_t FusionStorageIscsiDiskScanner::GetLoginedTargetIP(std::vector<std::string> &loginedIps)
{
    INFOLOG("Start to get logined target ip.");
    std::vector<std::string> cmdOut;
    std::string agentHomedir = Module::EnvVarManager::GetInstance()->GetAgentHomePath();
    std::vector<Module::CmdParam> cmdParam{
        Module::CmdParam(Module::COMMON_CMD_NAME, "sudo"),
        Module::CmdParam(Module::SCRIPT_CMD_NAME, agentHomedir + SUDO_DISK_TOOL_PATH),
        "rescan_iscsi_session"
    };
    if (Utils::CallAgentExecCmd(cmdParam, cmdOut) != 0) {
        WARNLOG("Get login target ip failed.");
        return FAILED;
    }
    if (cmdOut.empty()) {
        WARNLOG("Logined target ip count is 0.");
        return SUCCESS;
    }
    Defer _(nullptr, [&](...) { Module::FreeContainer(cmdOut); });

    for (const auto &iscsiSession : cmdOut) {
        int32_t startPos = iscsiSession.find(ISCSI_SESSION_PORTAL);
        if (startPos == std::string::npos) {
            ERRLOG("Failed to get the portal in the session.");
            break;
        }
        std::string portal = iscsiSession.substr(startPos + ISCSI_SESSION_PORTAL_OFFSET);
        int32_t endPos = portal.find(",");
        if (endPos == std::string::npos) {
            ERRLOG("Failed to get the port in the iscsi portal.");
            break;
        }
        std::string loginedIp = portal.substr(1, endPos - 1);
        loginedIps.push_back(loginedIp);
    }

    INFOLOG("Get logined target ip success.");
    return SUCCESS;
}

int32_t FusionStorageIscsiDiskScanner::GetLoginedNodeIP(std::vector<std::string> &nodeIPs)
{
    std::vector<std::string> cmdOut;
    std::string agentHomedir = Module::EnvVarManager::GetInstance()->GetAgentHomePath();
    std::vector<Module::CmdParam> cmdParam {
        Module::CmdParam(Module::COMMON_CMD_NAME, "sudo"),
        Module::CmdParam(Module::SCRIPT_CMD_NAME, agentHomedir + SUDO_DISK_TOOL_PATH),
        "get_loggedIn_nodeIp"
    };
    if (Utils::CallAgentExecCmd(cmdParam, cmdOut) != 0) {
        WARNLOG("Get login target ip error.");
        return Module::FAILED;
    }
    if (cmdOut.size() == 0) {
        WARNLOG("Logined node ip count is 0. ");
        return Module::SUCCESS;
    }
    for (std::vector<std::string>::iterator ite = cmdOut.begin(); cmdOut.end() != ite; ++ite) {
        nodeIPs.push_back(*ite);
        DBGLOG("Get logined target ip: %s", (*ite).c_str());
    }
    return Module::SUCCESS;
}

int32_t FusionStorageIscsiDiskScanner::DoScanAfterMapped(
    const std::string &volName, const std::string &volWwn, std::string &diskDevicePath, bool isInternalScence)
{
    DBGLOG("Enter");
    {
        std::lock_guard<std::mutex> lock(m_iscsiMutext);
        INFOLOG("Start to do scan to get the disk path after mapped.");
        diskDevicePath = GetDiskPathForWWN(volWwn);
        if (!diskDevicePath.empty()) {
            INFOLOG("Get disk device path after attach, wwn:%s, path:%s", volWwn.c_str(), diskDevicePath.c_str());
            return SUCCESS;
        }

        DiskScannerHandler::GetInstance()->RescanIscsiSession();
        DiskScannerHandler::GetInstance()->ScanScsiHost();
    }

    diskDevicePath = GetDiskPathForWWN(volWwn);
    if (!diskDevicePath.empty()) {
        INFOLOG("Get disk device path after attach, wwn:%s, path:%s", volWwn.c_str(), diskDevicePath.c_str());
        return SUCCESS;
    }
    diskDevicePath = DiskScannerHandler::GetInstance()->GetDiskPathForWwnInSysDir(volWwn);
    if (diskDevicePath.empty()) {
        ERRLOG("Get disk %s device path failed, its wwn is %s.", volName.c_str(), volWwn.c_str());
        return FAILED;
    }

    INFOLOG("Get disk device path after attach, wwn:%s, path:%s", volWwn.c_str(), diskDevicePath.c_str());
    return SUCCESS;
}

std::string FusionStorageIscsiDiskScanner::GetDiskPathForWWN(const std::string &volWwn)
{
    INFOLOG("Start to get disk path for wwn %s.", volWwn.c_str());
    std::string diskPath;
    std::vector<std::string> cmdOut;
    std::string agentHomedir = Module::EnvVarManager::GetInstance()->GetAgentHomePath();

    std::vector<Module::CmdParam> cmdParam{
        Module::CmdParam(Module::COMMON_CMD_NAME, "sudo"),
        Module::CmdParam(Module::SCRIPT_CMD_NAME, agentHomedir + SUDO_DISK_TOOL_PATH),
        "get_diskpath_for_wwn",
        Module::CmdParam(Module::COMMON_PARAM, volWwn)
    };
    if (Utils::CallAgentExecCmd(cmdParam, cmdOut) != 0) {
        ERRLOG("The device is not found, path:%s", volWwn.c_str());
        return diskPath;
    }
    INFOLOG("size %d", cmdOut.size());
    // 遍历buffer内容，解析出磁盘路径
    for (const auto &buff : cmdOut) {
        INFOLOG("=== %s", buff.c_str());
        std::string strLine(buff);
        std::string strLineCopy(buff);
        int nPos = strLine.find(D_FINDDISK);
        if (nPos == std::string::npos) {
            return diskPath;
        }
        strLine.erase(strLine.find_last_not_of("\n") + 1);
        strLine.erase(0, nPos);
        strLine = "/dev/disk/by-id/" + strLine;
        int nPosWWN = strLineCopy.find("scsi-");
        if (nPosWWN == std::string::npos) {
            nPosWWN = strLineCopy.find("wwn-");
            if (nPosWWN == std::string::npos) {
                continue;
            }
        }

        strLineCopy.erase(0, nPosWWN);
        int nPosSpace = strLineCopy.find(" ");
        strLineCopy.erase(nPosSpace);
        strLineCopy.erase(0, WWN_POS_OFFSET);
        INFOLOG("Disk path:%s, line copy:%s", strLine.c_str(), strLineCopy.c_str());
        return strLine;
    }

    INFOLOG("Get disk path for wwn success.");
    return diskPath;
}

int32_t FusionStorageIscsiDiskScanner::DeleteDiskFromPathSet(
    const std::string &volName, const std::string &targetDiskPath)
{
    DBGLOG("Start to delete iscsi disk from path set.");

    DiskScannerHandler::GetInstance()->RescanIscsiSession();
    // 去除前缀
    std::string targetDiskName = targetDiskPath;
    if (targetDiskPath.rfind("/") != END_FIND_POS) {
        int nPos = targetDiskName.rfind("/");
        targetDiskName = targetDiskName.substr(nPos + 1);
        INFOLOG("The target disk name is %s.", targetDiskName.c_str());
    } else {
        INFOLOG("Failed to get disk name from disk path: %s.", targetDiskPath.c_str());
        return FAILED;
    }
    // 扫描获得路径集合
    std::set<std::string> diskPathSet;
    int32_t ret = GetIscsiDiskPathSet(diskPathSet);
    if (ret != SUCCESS) {
        ERRLOG("Failed to get iscsi disk path set.");
        return FAILED;
    }
    // 从路径集合中删除
    bool isExist = false;
    ret = DoDeleteDiskFromPathSet(diskPathSet, targetDiskName, isExist);
    if (ret != SUCCESS || isExist != false) {
        WARNLOG("Failed to delete iscsi vol from path set, volName: %s, volDiskName: %s",
            volName.c_str(),
            targetDiskName.c_str());
    }
    if (!isExist) {
        INFOLOG("Delete the vol from path set success.");
        return SUCCESS;
    }

    DiskScannerHandler::GetInstance()->RescanIscsiSession();
    DBGLOG("Failed to delete the vol from path set.");
    return FAILED;
}

int32_t FusionStorageIscsiDiskScanner::GetIscsiDiskPathSet(std::set<std::string> &diskPathSet)
{
    INFOLOG("Start to get iscsi disk path set.");
    std::vector<std::string> cmdOut;
    std::string agentHomedir = Module::EnvVarManager::GetInstance()->GetAgentHomePath();
    std::vector<Module::CmdParam> cmdParam{
        Module::CmdParam(Module::COMMON_CMD_NAME, "sudo"),
        Module::CmdParam(Module::SCRIPT_CMD_NAME, agentHomedir + SUDO_DISK_TOOL_PATH),
        "get_scsi_list"
    };
    if (Utils::CallAgentExecCmd(cmdParam, cmdOut) != 0) {
        ERRLOG("The lsscsi command is not enable in the environment!");
        return FAILED;
    }

    diskPathSet.clear();
    for (const std::string &buff : cmdOut) {  // 逐行读取，获取盘符
        std::string strLine(buff);
        int nPos = strLine.find("/dev/");
        if (nPos != std::string::npos) {
            strLine.erase(strLine.find_last_not_of("\n") + 1);
            strLine.erase(0, nPos);
            strLine.erase(strLine.end() - 1);  // 去除最后一位空格
            diskPathSet.insert(strLine);
        }
    }

    INFOLOG("Get disk Path set success.");
    return SUCCESS;
}

int32_t FusionStorageIscsiDiskScanner::DoDeleteDiskFromPathSet(
    std::set<std::string> &diskPathSet, const std::string &targetDiskName, bool isExist)
{
    INFOLOG("Start to delete disk path %s from path set.", targetDiskName.c_str());
    std::set<std::string>::iterator it;
    std::string agentHomedir = Module::EnvVarManager::GetInstance()->GetAgentHomePath();
    for (it = diskPathSet.begin(); it != diskPathSet.end(); ++it) {
        std::string strDiskPath = *it;
        int nPos = strDiskPath.rfind("/");
        std::string strDiskName = strDiskPath.substr(nPos + 1);
        // 找到后执行删除
        if (strDiskName == targetDiskName) {
            isExist = true;
            
            std::vector<std::string> cmdOut;
            std::unordered_set<std::string> pathWhitelist;
            pathWhitelist.insert(strDiskName);
            std::vector<Module::CmdParam> cmdParam{
                Module::CmdParam(Module::COMMON_CMD_NAME, "sudo"),
                Module::CmdParam(Module::SCRIPT_CMD_NAME, agentHomedir + SUDO_DISK_TOOL_PATH),
                "dev_delete",
                Module::CmdParam(Module::PATH_PARAM, strDiskName)
            };
            if (Utils::CallAgentExecCmd(cmdParam, cmdOut) != 0) {
                ERRLOG("Delete disk failed, path: %s", strDiskName.c_str());
                return FAILED;
            }

            INFOLOG("Delete disk success, path: %s", strDiskName.c_str());
            return SUCCESS;
        }
    }

    INFOLOG("No target disk in the path set, maybe deleted by outside environment.");
    return SUCCESS;
}

VIRT_PLUGIN_NAMESPACE_END  // namespace VirtPlugin